package br.com.wagnerpaz.javahook.win32;

import static br.com.wagnerpaz.javahook.NativeMouseEvent.BUTTON1;
import static br.com.wagnerpaz.javahook.NativeMouseEvent.BUTTON2;
import static br.com.wagnerpaz.javahook.NativeMouseEvent.BUTTON3;
import static br.com.wagnerpaz.javahook.NativeMouseEvent.MOUSE_MOVED;
import static br.com.wagnerpaz.javahook.NativeMouseEvent.MOUSE_PRESSED;
import static br.com.wagnerpaz.javahook.NativeMouseEvent.MOUSE_RELEASED;
import static br.com.wagnerpaz.javahook.NativeMouseEvent.MOUSE_WHEEL;
import static br.com.wagnerpaz.javahook.NativeMouseEvent.NOBUTTON;
import static br.com.wagnerpaz.javahook.NativeMouseWheelEvent.WHEEL_BLOCK_SCROLL;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WH_KEYBOARD_LL;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WH_MOUSE_LL;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_KEYDOWN;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_KEYUP;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_LBUTTONDOWN;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_LBUTTONUP;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_MBUTTONDOWN;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_MBUTTONUP;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_MOUSEMOVE;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_MOUSEWHEEL;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_RBUTTONDOWN;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_RBUTTONUP;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_SYSKEYDOWN;
import static br.com.wagnerpaz.javahook.win32.jna.User32.WM_SYSKEYUP;

import java.awt.event.KeyEvent;

import br.com.wagnerpaz.javahook.NativeKeyboardEvent;
import br.com.wagnerpaz.javahook.NativeKeyboardListener;
import br.com.wagnerpaz.javahook.NativeMouseEvent;
import br.com.wagnerpaz.javahook.NativeMouseListener;
import br.com.wagnerpaz.javahook.NativeMouseWheelEvent;
import br.com.wagnerpaz.javahook.win32.jna.Kernel32;
import br.com.wagnerpaz.javahook.win32.jna.User32;
import br.com.wagnerpaz.javahook.win32.jna.User32.HHOOK;
import br.com.wagnerpaz.javahook.win32.jna.User32.KBDLLHOOKSTRUCT;
import br.com.wagnerpaz.javahook.win32.jna.User32.LowLevelKeyboardProc;
import br.com.wagnerpaz.javahook.win32.jna.User32.LowLevelMouseProc;
import br.com.wagnerpaz.javahook.win32.jna.User32.MSG;
import br.com.wagnerpaz.javahook.win32.jna.User32.MSLLHOOKSTRUCT;
import br.com.wagnerpaz.javahook.win32.jna.W32API.HMODULE;
import br.com.wagnerpaz.javahook.win32.jna.W32API.LRESULT;
import br.com.wagnerpaz.javahook.win32.jna.W32API.WPARAM;

/**
 * <p>Contains utility methods to install/uninstall
 * system hooks on Windows OS.</p>
 * 
 * @author Wagner Paz
 * @since 1.0
 */
public class Win32HookManager
{
	private static HHOOK keyboardHook;
	private static HHOOK mouseHook;
	
	/**
	 * Install a system hook that listen to keyboard input data.
	 * 
	 * @param listener The listener. Will receive every key notification.
	 * @return true case the hook was installed sucefully.
	 */
	public static boolean installKeyboardHook(final NativeKeyboardListener listener)
	{
		final User32  lib  = User32.INSTANCE;
		final HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
		
		final LowLevelKeyboardProc keyboardHookProc = new LowLevelKeyboardProc()
		{
			public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT info)
			{
				NativeKeyboardEvent ev = null;
				long                ti = System.currentTimeMillis();
				boolean             nh = true;
				
				if (nCode >= 0)
				{
					switch ( wParam.intValue() )
					{
						case WM_KEYDOWN:
						case WM_SYSKEYDOWN:
							ev = new NativeKeyboardEvent(KeyEvent.KEY_PRESSED, ti, 0, info.vkCode);
							nh = listener.keyPressed(ev);
							break;
							
						case WM_KEYUP:
						case WM_SYSKEYUP:
							ev = new NativeKeyboardEvent(KeyEvent.KEY_RELEASED, ti, 0, info.vkCode);
							nh = listener.keyReleased(ev);
							break;
					}
				}
				
				if(nh)
				{
					return lib.CallNextHookEx(keyboardHook, nCode, wParam, info.getPointer());
				}
				return new LRESULT(1);
			}
		};
		
		new Thread()
		{
			@Override
			public void run()
			{
				keyboardHook = lib.SetWindowsHookEx(WH_KEYBOARD_LL, keyboardHookProc, hMod, 0);
				msgLoop();
				lib.UnhookWindowsHookEx(keyboardHook);
			}
		}.start();
		
		return keyboardHook != null;
	}
	
	/**
	 * Uninstall a keyboard system hook.
	 * 
	 * @return true case the hook was uninstalled sucefully.
	 */
	public static boolean uninstallKeyboardHook()
	{
		if(keyboardHook != null)
		{
			return User32.INSTANCE.UnhookWindowsHookEx(keyboardHook);
		}
		
		return false;
	}
	
	/**
	 * Install a system hook that listen to mouse input data.
	 * 
	 * @param listener The listener. Will receive every mouse notification.
	 * @return true case the hook was installed sucefully.
	 */
	public static boolean installMouseHook(final NativeMouseListener listener)
	{
		final User32  lib  = User32.INSTANCE;
		final HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
		
		final LowLevelMouseProc mouseHookProc = new LowLevelMouseProc()
		{
			public LRESULT callback(int nCode, WPARAM wParam, MSLLHOOKSTRUCT info)
			{
				NativeMouseEvent      ev  = null;
				NativeMouseWheelEvent evw = null;
				
				long             ti = System.currentTimeMillis();
				boolean          nh = true;
				
				if (nCode >= 0)
				{
					switch (wParam.intValue())
					{
						case WM_LBUTTONDOWN:
							ev = new NativeMouseEvent(MOUSE_PRESSED, ti, 0, info.pt.x, info.pt.y, BUTTON1);
							nh = listener.mousePressed(ev);
							break;
						
						case WM_MBUTTONDOWN:
							ev = new NativeMouseEvent(MOUSE_PRESSED, ti, 0, info.pt.x, info.pt.y, BUTTON2);
							nh = listener.mousePressed(ev);
							break;
						
						case WM_RBUTTONDOWN:
							ev = new NativeMouseEvent(MOUSE_PRESSED, ti, 0, info.pt.x, info.pt.y, BUTTON3);
							nh = listener.mousePressed(ev);
							break;
						
						case WM_LBUTTONUP:
							ev = new NativeMouseEvent(MOUSE_RELEASED, ti, 0, info.pt.x, info.pt.y, BUTTON1);
							nh = listener.mouseReleased(ev);
							break;
						
						case WM_MBUTTONUP:
							ev = new NativeMouseEvent(MOUSE_RELEASED, ti, 0, info.pt.x, info.pt.y, BUTTON2);
							nh = listener.mouseReleased(ev);
							break;
						
						case WM_RBUTTONUP:
							ev = new NativeMouseEvent(MOUSE_RELEASED, ti, 0, info.pt.x, info.pt.y, BUTTON3);
							nh = listener.mouseReleased(ev);
							break;
						
						case WM_MOUSEMOVE:
							ev = new NativeMouseEvent(MOUSE_MOVED, ti, 0, info.pt.x, info.pt.y, NOBUTTON);
							nh = listener.mouseMoved(ev);
							break;
							
						case WM_MOUSEWHEEL:
							evw = new NativeMouseWheelEvent(MOUSE_WHEEL, ti, info.pt.x, info.pt.y, WHEEL_BLOCK_SCROLL, 0, 1);
							nh  = listener.mouseWheelMoved(evw);
							break;
					}
				}
				
				if(nh)
				{
					return lib.CallNextHookEx(mouseHook, nCode, wParam, info.getPointer());
				}
				return new LRESULT(1);
			}
		};
		
		new Thread()
		{
			@Override
			public void run()
			{
				mouseHook = lib.SetWindowsHookEx(WH_MOUSE_LL, mouseHookProc, hMod, 0);
				msgLoop();
				lib.UnhookWindowsHookEx(mouseHook);
			}
		}.start();
		
		return mouseHook != null;
	}
	
	/**
	 * Uninstall a mouse system hook.
	 * 
	 * @return true case the hook was uninstalled sucefully.
	 */
	public static boolean uninstallMouseHook()
	{
		if(mouseHook != null)
		{
			return User32.INSTANCE.UnhookWindowsHookEx(mouseHook);
		}
		
		return false;
	}
	
	private static void msgLoop()
	{
		final User32 lib = User32.INSTANCE;
		
		int result;
		MSG msg = new MSG();
		while ((result = lib.GetMessage(msg, null, 0, 0)) != 0)
		{
			if (result == -1)
			{
				System.err.println("error in get message");
				break;
			}
			else
			{
				System.err.println("got message");
				lib.TranslateMessage(msg);
				lib.DispatchMessage(msg);
			}
		}
	}
}
